home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1998 March / Macworld (1998-03) (Disk 1).dmg / Shareware World / Utilities / Text Processing / Alpha / Tcl / Packages / elecCompletion.tcl < prev    next >
Encoding:
Text File  |  1997-12-08  |  16.1 KB  |  452 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #    Vince's    Additions -    an extension package for Alpha
  4.  # 
  5.  #    FILE: "elecCompletion.tcl"
  6.  #                                      created: 8/3/96 {12:06:40 pm}    
  7.  #                                  last update: 5/12/97 {3:39:59 pm}    
  8.  #    Author:    Vince Darley
  9.  #    E-mail:    <darley@fas.harvard.edu>
  10.  #      mail:    Division of    Applied    Sciences, Harvard University
  11.  #            Oxford Street, Cambridge MA    02138, USA
  12.  #       www:    <http://www.fas.harvard.edu/~darley/>
  13.  #    
  14.  #  modified by  rev reason
  15.  #  -------- --- --- -----------
  16.  #  8/3/96   VMD 1.0 original
  17.  #  20/11/96 VMD 1.1 many, many improvements.
  18.  #  24/2/97  VMD 1.2 added some support of trf's code, plus some fixes
  19.  #  1/9/97   VMD 1.5 added 'completion::contraction' and improved g-elec.
  20.  #  12/1/97  trf 1.6 added 'Tutorial Shell' stuff, bumped to 9.0b2
  21.  #  12/2/97  trf 1.7 corrected corrections, bumped to 9.0b3
  22.  #  4/12/97  VMD 1.8 various fixes, better tcl8 compatibility
  23.  # ###################################################################
  24.  ##
  25.  
  26. alpha::extension elecCompletions 9.0b3 {
  27.     alpha::package require elecBindings 9.0b2
  28.     menu::insert mode items end "completionsTutorial" "editCompletions" 
  29.     # load completion code for a mode the first time that mode is used
  30.     hook::register mode::init completion::load "*"
  31.     namespace eval completion {}
  32.     completion::initialise
  33. } maintainer {
  34.     "Vince Darley" darley@fas.harvard.edu <http://www.fas.harvard.edu/~darley/>
  35. } uninstall this-file help {file "ElecCompletions Help"}
  36.  
  37. proc completion::initialise {} {}
  38.  
  39. namespace eval elec {}
  40.  
  41. proc completion::load {} {
  42.     global HOME
  43.     foreach f [glob -nocomplain ${HOME}:Tcl:Completions:[modeALike]Completions*.tcl] {
  44.         message "loading [file tail $f]…"
  45.         namespace eval ::[modeALike]::Completion {}
  46.         uplevel \#0 [list source $f]
  47.     }
  48. }
  49.  
  50. ## 
  51.  # -------------------------------------------------------------------------
  52.  # 
  53.  #    "completion::cmd"    --
  54.  # 
  55.  #     General purpose proc for extending    a given    command    to its full    extent
  56.  #     in    a mode-dependent fashion.  If we hit a unique match, we    call
  57.  #     '${mode}completion::Electric'; if we    can    extend,    we do so, and set things up
  58.  #     so    the    calling    procedure '${mode}completion::Cmd' will be called    if
  59.  #     the user tries    to cmd-Tab again; if we    don't recognise    anything,
  60.  #     we    return 0
  61.  #     
  62.  #     We    normally use the list ${m}cmds to look for completions,    but    the
  63.  #     caller    can    supply a different name.  This is useful to    prioritise
  64.  #     lists,    so we first    call with one, then    another,...     I currently use
  65.  #     this feature for TeX-completions, in which    I call with    a second list,
  66.  #     containing    fake commands, which expand    into environments.
  67.  # -------------------------------------------------------------------------
  68.  ##
  69. proc completion::cmd { {cmd ""} {listExt "cmds"} {prematch ""}} {
  70.     if ![string length $cmd] { 
  71.         set cmd [completion::lastWord]
  72.         # if there's any whitespace in the command then it's no good to us
  73.         if [containsSpace $cmd] { return 0 }
  74.     }
  75.  
  76.     set m [modeALike]
  77.     # do an electric if we already match exactly
  78.     global ${m}electrics
  79.     if [info exists ${m}electrics($cmd)] {
  80.         return [completion ${m} Electric "${prematch}${cmd}"]
  81.     }
  82.     if { [set matches [completion::modeList $cmd ${m}${listExt}]] == 0 } {
  83.         return 0
  84.     } else {
  85.         return [completion::matchUtil Cmd $cmd $matches $prematch]
  86.     }
  87. }
  88.  
  89. proc completion::matchUtil {proc what matches {prematch ""}} {
  90.     if {$matches == ""} { return 0 }
  91.     set match [completion::Find $what $matches]
  92.     if [string length $match] {
  93.         # we completed or cancelled, so move on
  94.         completion::already error
  95.         if { $match == 1 } {
  96.             return 1
  97.         } else {
  98.             return [completion [modeALike] Electric "${prematch}${match}"]
  99.         }
  100.     } else {
  101.         completion::already $proc
  102.         return 1
  103.     }
  104. }
  105.  
  106. ## 
  107.  # -------------------------------------------------------------------------
  108.  #     
  109.  # "completion::ensemble"    --
  110.  #    
  111.  #    Complete and do    electrics for commands which have two parts    separated
  112.  #    by a space.     Very useful for Tcl's "string compare ..."    etc.
  113.  # -------------------------------------------------------------------------
  114.  ##
  115. proc completion::ensemble {dummy} {
  116.     set lastword [completion::lastTwoWords prevword]
  117.     set prevword [string trim $prevword]
  118.     set m [modeALike]
  119.     global ${m}${prevword}cmds
  120.     if [info exists ${m}${prevword}cmds] {
  121.         return [completion::cmd $lastword "${prevword}cmds" "${prevword} "]
  122.     } else {
  123.         return 0
  124.     }
  125. }
  126.  
  127.  
  128. ## 
  129.  # -------------------------------------------------------------------------
  130.  #     
  131.  #    "completion::modeList" --
  132.  #    
  133.  #     Given a 'cmd' prefix and the name of a    list to    search,    that list
  134.  #     being stored in alphabetical order    and    starting/ending    with 
  135.  #     whitespace, this proc returns a list of all matches with 'cmd',
  136.  #     or    '0'    if there were none.  Updated so works with arrays too (Nov'96)
  137.  # -------------------------------------------------------------------------
  138.  ##
  139. proc completion::modeList { cmd slist } {
  140.     global [lindex [split $slist "\("] 0]
  141.     # Find all matches as a list --- a v. clever trick if I say so myself
  142.     set reg "\[ \n\r\t\]+(${cmd}\[^ \n\r\t\]*\[ \n\r\t\]+)+"
  143.     if [regexp $reg [set "$slist"] matches] {
  144.         return $matches
  145.     } else {
  146.         return 0
  147.     }
  148. }
  149.  
  150. ## 
  151.  # -------------------------------------------------------------------------
  152.  #     
  153.  #    "completion::electric" --
  154.  #    
  155.  #     Given a command, and an optional list of defaults,    check the command
  156.  #     is    ok and if so try and insert    an electric    entry.
  157.  # -------------------------------------------------------------------------
  158.  ##
  159. proc completion::electric { {cmd ""} args } {
  160.     set m [modeALike]
  161.     if ![string length $cmd] { 
  162.         set cmd [completion::lastWord] 
  163.         # only check for space if we're doing it
  164.         if [containsSpace $cmd] { return 0 }
  165.     }
  166.     
  167.     return [eval [list elec::findCmd $cmd ${m}electrics] $args]
  168. }
  169.  
  170. ## 
  171.  # -------------------------------------------------------------------------
  172.  #     
  173.  # "completion::contraction"    --
  174.  #    
  175.  #    Complete and do    electrics for commands which have two parts    separated
  176.  #    by a apostrophe.   Useful for making shortcuts to things. ex: s'c Tcl's 
  177.  #    "string compare ..."    etc.
  178.  # -------------------------------------------------------------------------
  179.  ##
  180. proc completion::contraction {dummy} {
  181.     set lastword [completion::lastTwoWords hint]
  182.     if {![regexp "'\$" $hint]} {return 0}
  183.     append hint $lastword
  184.     return [completion::electric $hint]
  185. }
  186.  
  187. ## 
  188.  # -------------------------------------------------------------------------
  189.  # 
  190.  #    "elec::findCmd" --
  191.  # 
  192.  #     General purpose proc for extending    a command in some predetermined    
  193.  #     fashion (such as mapping 'for'    to a template 'for (;;)…').     Mode specific 
  194.  #     procedures    may    use    this if    desired.  The given    command    is looked up in    
  195.  #     the given array '$arrayn',    and    if there is    an entry, some electric    
  196.  #     procedure happens.     By    default, if    an entry is    '0', then '0' is returned 
  197.  #     (which    can    be used    by the calling procedure to    take some other    action,    
  198.  #     usually more sophisticated    such as    TeX-ref- completion), and if the entry 
  199.  #     is    an integer corresponding to    a list element of the list 'args', then    
  200.  #     that element is inserted.    In this    case list elements start with '1' 
  201.  #     (because zero has a special meaning).    Template stops in the electric 
  202.  #     completion    are    marked by pairs    of bullets '••'.  If there is any text 
  203.  #     between the bullets, that can be used to inform the user of what ought    to 
  204.  #     go    there.    All    strings    must contain at    least one such template    stop, to 
  205.  #     which the insertion point moves.
  206.  # 
  207.  #    '$arrayn' ought    not    to be a    large array    or this    proc may be slow.
  208.  #  (we first look for an exact array element match $arrayn($cmd), but
  209.  #  if that fails we look for a glob'ed match)
  210.  #  
  211.  #  The array element may contain control sequences.  These start with
  212.  #  '◊', and may be followed by:
  213.  #  
  214.  #  kill0 --- delete the string which triggered this template before
  215.  #            inserting anything.
  216.  #            
  217.  #  killN --- delete all except N characters of the string.
  218.  #  
  219.  #  N --- use the N'th element of 'args' for the template.
  220.  #  
  221.  #  [ --- the string must be evaluated first (usually triggering some proc
  222.  #        which perhaps interacts with the user a bit)
  223.  #  
  224.  #  » --- an indirection; use the template insertion corresponding to
  225.  #        the given text item instead.
  226.  #        
  227.  #  In order to provide backward compatiblity of this proc with any new 
  228.  #  control sequences that may be developed, any 'unknown' control 
  229.  #  sequence is just deleted, a package that deals with the new sequences 
  230.  #  thus has to overide this proc in order to make the now sequences 
  231.  #  functionality available.
  232.  #  
  233.  #  So, what are some of the possible future control sequences? Well, I've 
  234.  #  played with;
  235.  #  
  236.  #                 sequences bound to a stop
  237.  #  
  238.  #  « --- an extended prompt, provides a longer, more pedalogical explanation 
  239.  #        for a stop that the curt, fill in 'xxx' in the statusline.
  240.  #  ¶ --- a name that acts as an index into an array of code snippets, so a 
  241.  #        bit of code can be executed when visiting a stop, perhaps aiding 
  242.  #        in filling in options, validating entries, or anything else that 
  243.  #        makes sense.
  244.  #  ø --- marks a stop of such an obvious nature, that the marking of the 
  245.  #        stop with a dot, or and in-text prompt is superflous. In fact, such 
  246.  #        stops often have existing statements dragged into their position, 
  247.  #        so leaving them unmarked has a speed advantage. Perhaps this 
  248.  #        action is best toggled depending on a flag value.
  249.  #        
  250.  #     Any stop that falls in the above class, will occur after any regular 
  251.  #     prompting text, and should trigger the removal of itself and any 
  252.  #     other characters up until the occurrence of the stop ending bullet. 
  253.  #     That can be acomplished in one of two ways, here with a regsub of this 
  254.  #     form:
  255.  #     regsub -all {•([^◊]*)◊[^•]+•} <template> {•\1•} result 
  256.  #     or by applying the regsub to the entire set of electrics for a mode 
  257.  #     as soon as its completions are loaded. (first method implemented)
  258.  #        
  259.  #                 sequences that occurr at the start of a template
  260.  #                     and apply to the template as a whole
  261.  #  
  262.  #  < --- means that certain conditions that must be meet by the text 
  263.  #        proceeding where this template is to be inserted must be met 
  264.  #        before the insertion is allowed, (e.g. a tcl command must be 
  265.  #        proceeded by whitespace, a [, a ", or eval for the insertion 
  266.  #        to be syntactically correct and thus , allowable)
  267.  #        
  268.  #     Sequences in this class will have to be of a single character, as 
  269.  #     will get rid of any unknown sequence by
  270.  #     resub {◊[^k0-9»\[]} [string range <template 0 [string first • <template>]] head
  271.  #     set <template> $head
  272.  #     append <template> rest
  273.  #
  274.  #  Includes some fixes by Tom Fetherston
  275.  # -------------------------------------------------------------------------
  276.  ##
  277. proc elec::findCmd { cmd arrayn args } {
  278.     if {[set action [elec::_findCmd $cmd $arrayn]] == ""} { return 0 }
  279.     # we have the action; check for control sequences
  280.     while {[string index $action 0] == "◊"} {
  281.         # control sequence: kill, procedure or choice of default value?
  282.         set action [string range $action 1 end]
  283.         if { [string range $action 0 3] == "kill" } {
  284.             set dpos [expr [getPos] - [string length $cmd] - [string index $action 4]] 
  285.             deleteText $dpos [getPos]
  286.             regsub -all "kill" [string range $action 5 end] $cmd action
  287.         } elseif {[string index $action 0] == "\[" } {
  288.                 set action [subst $action]
  289.         } elseif {[string index $action 0] == "»" } {
  290.             set key [string range $action 1 end]
  291.             global $arrayn
  292.             set text [set ${arrayn}($key)]
  293.             set action "◊kill0${key}${text}" 
  294.         } elseif {([scan $action %d idx]) \
  295.           && (![ catch {lindex $args [expr $idx-1]} act]) } {
  296.             set action $act
  297.         } else {
  298.             if {[info commands [set proc elec::action::[string index $action 1]]] == $proc} {
  299.                 set action [$proc $action]
  300.             } else {
  301.                 set action [string range $action 2 end]
  302.             }
  303.         }
  304.     }
  305.     # then, we pull out any "bulleted-stop control sequences" that are 
  306.     # unknown to this version of elec::findCmd -trf
  307.     regsub -all {•([^◊]*)◊[^•]+•} $action {•\1•} action 
  308.     elec::Insertion $action
  309.         # The idea here is to continue with other completions (return 0)
  310.         # if the character before the insertion point is non white-space
  311.     global wordBreakPreface
  312.     if {![regexp $wordBreakPreface [lookAt [expr [getPos] -1]]]} {
  313.         if [isSelection] {deleteText [getPos] [selEnd]}
  314.         return 0
  315.     } else {
  316.         return 1
  317.     }
  318. }
  319.  
  320. ## 
  321.  # -------------------------------------------------------------------------
  322.  # 
  323.  # "elec::_findCmd" --
  324.  # 
  325.  #  Find the electric command in the given array, or return ""
  326.  # -------------------------------------------------------------------------
  327.  ##
  328. proc elec::_findCmd {cmd arrayn} {
  329.     global $arrayn
  330.     if [info exists ${arrayn}($cmd)] {
  331.         return [set "${arrayn}($cmd)"]
  332.     } else {
  333.         if {[string first "*" [set elec_ar [array names $arrayn]]] != -1 } {
  334.             # some of the array matches are glob'ed; we must go one at a time
  335.             foreach elec $elec_ar {
  336.                 if [string match $elec $cmd] {
  337.                     return [set "${arrayn}($elec)"]
  338.                 }
  339.             }
  340.         }
  341.     }
  342.     return ""
  343. }
  344.  
  345. # just so we have one!
  346. set userCompletions(date) {◊kill0◊[lindex [mtime [now]] 0]}
  347.  
  348. # ensure old version loaded:
  349. catch "completion::user"
  350. ## 
  351.  # -------------------------------------------------------------------------
  352.  # 
  353.  # "completion::user" --
  354.  # 
  355.  #   A user completion is used for small
  356.  #     mode-independent snippets, like your email address, name etc.
  357.  #     For instance I have the following defined:
  358.  #     
  359.  #     set userCompletions(vmd) "◊kill0Vince Darley"
  360.  #   set userCompletions(www) "◊kill0<[icGetPref WWWHomePage]>"
  361.  #   set userCompletions(e-) "◊kill0<[icGetPref Email]>"
  362.  #   
  363.  #   Here '◊kill0' is a control sequence which means kill exactly what
  364.  #   I just typed before carrying out this completion.
  365.  # -------------------------------------------------------------------------
  366.  ##
  367. proc completion::user { {cmd ""} } {
  368.     if ![string length $cmd] { set cmd [completion::lastWord] }
  369.     if [containsSpace $cmd] { return 0 }
  370.  
  371.     return [elec::findCmd $cmd userCompletions]    
  372. }
  373.  
  374. proc mode::completionsTutorial {} {
  375.     global HOME
  376.     set f "${HOME}:Tcl:Completions:[modeALike] Tutorial"
  377.     set files [glob -nocomplain $f*]
  378.     if {[llength $files] == 1} {
  379.         set fName [lindex $files 0]
  380.         set mode [file::whichModeForWin "dummy[file extension $fName]"]
  381.         set t [readFile $fName]
  382.         new -n "*Tutorial shell*" -m $mode
  383.         setWinInfo shell 1
  384.         insertText $t
  385.         unset t
  386.         goto 0
  387.         bind 0x32 vsp $mode
  388.     } else {
  389.         alertnote "No tutorial exists for this mode.  Why don't you write one?"
  390.     }
  391. }
  392.  
  393. proc vsp {} {
  394.     if {[win::Current] != "*Tutorial shell*"} {
  395.         typeText "`"
  396.         return
  397.     } 
  398.     searchString "◊" 
  399.     goto [expr [getPos]+2] 
  400.     findAgain
  401.     if [isSelection] {
  402.         deleteText [getPos] [selEnd]
  403.         # add the following to prevent the 'non-use' of a template from
  404.         # messing up the next completion
  405.         ring::clear
  406.     }    
  407. }
  408.  
  409. proc mode::editCompletions {} {
  410.     global HOME
  411.     set f ${HOME}:Tcl:Completions:[modeALike]Completions.tcl
  412.     if [catch {openFileQuietly $f}] {
  413.         beep
  414.         if {[askyesno "No completions exist for this mode. Do you want to create some?"] == "yes"} {
  415.             set fd [open $f "w"]
  416.             close $fd
  417.             edit $f
  418.             insertText {## 
  419.  # This file will be sourced automatically, immediately after 
  420.  # the _first_ time the file which defines its mode is sourced.
  421.  # Use this file to declare completion items and procedures
  422.  # for this mode.
  423.  # 
  424.  # Some common defaults are included below.
  425.  ##
  426.  
  427. ## 
  428.  # These declare, in order, the names of the completion
  429.  # procedures for this mode.  The actual procedure
  430.  # must be named '${mode}Completion::${listItem}', unless
  431.  # the item is 'completion::*' in which case that actual
  432.  # procedure is called.  The procedure 'modeALike' will
  433.  # map modes to similar modes so procs don't need to be
  434.  # repeated.  However each mode requires its own array entry
  435.  # here.
  436.  ##
  437. set completions(<mode>) {contraction completion::cmd Ensemble completion::electric Var}
  438.  
  439. }\
  440.  {# ◊◊◊◊ Data for <mode> completions ◊◊◊◊ #
  441.  
  442. # cmds to be completed to full length (no need for short ones)
  443. set <mode>cmds { class default enum register return struct switch typedef volatile while }
  444. # electrics
  445. set <mode>electrics(for) " \{•start•\} \{•test•\} \{•increment•\} \{\r\t•body•\r\}\r••"
  446. set <mode>electrics(while) " \{•test•\} \{\r\t•body•\r\}\r••"
  447. # contractions
  448. set <mode>electrics(s'c) "◊»string compare"
  449. set <mode>electrics(s'f) "◊»string first"
  450. }}}            
  451. }
  452.